use core::fmt;
use std::{
    hash::{DefaultHasher, Hash, Hasher},
    str::FromStr,
};

use base64::{Engine, engine::general_purpose::URL_SAFE_NO_PAD};

pub struct ErrorInfo<T> {
    pub app_code: T, // could be HTTP 400 request
    pub code: &'static str,
    pub hash: String,
    pub cli_msg: &'static str,
    pub svr_msg: String,
}

pub trait ToErrorInfo {
    type T: FromStr;
    fn to_error_info(&self) -> ErrorInfo<Self::T>;
}

impl<T> ErrorInfo<T>
where
    T: std::str::FromStr,
    <T as FromStr>::Err: fmt::Debug,
{
    pub fn new(
        app_code: &str,
        code: &'static str,
        cli_msg: &'static str,
        svr_msg: impl fmt::Display,
    ) -> Self {
        let svr_msg = svr_msg.to_string();
        let mut hasher = DefaultHasher::new();
        svr_msg.hash(&mut hasher);
        let hash = hasher.finish();
        let hash = URL_SAFE_NO_PAD.encode(hash.to_be_bytes());
        Self {
            app_code: T::from_str(app_code).expect("can not parse app code"),
            code,
            hash,
            cli_msg,
            svr_msg: svr_msg.to_string(),
        }
    }
}

impl<T> ErrorInfo<T> {
    pub fn cli_msg(&self) -> &str {
        if self.cli_msg.is_empty() {
            &self.svr_msg
        } else {
            self.cli_msg
        }
    }
}

impl<T> fmt::Display for ErrorInfo<T> {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "[{}-{}] {}", self.code, self.hash, self.cli_msg())
    }
}

impl<T> fmt::Debug for ErrorInfo<T> {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "Error: [{}-{}] {}", self.code, self.hash, self.svr_msg)
    }
}
